﻿using DS_2021_0X28.PLC;
using DS_2024_02.Data;
using EVEBattery.DB;
using EVEBatteryCheck.Comm;
using EVEBatteryCheck.Data;
using EVEBatteryCheck.Entity;
using EVEBatteryCheck.ViewModels;
using EVEBatteryCheck.Views;
using GalaSoft.MvvmLight.Threading;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization.Formatters;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using TYBase;
using TYComm_For_DSApp.View;
using TYControl;
using TYControl_For_RTU;
using TYPLC.Kep;
using TYPLC.MX;
using TYPLC.PLC;
using TYRTUBase;
using static System.Runtime.CompilerServices.RuntimeHelpers;
using MessageBox = System.Windows.MessageBox;
using Path = System.IO.Path;

namespace EVEBatteryCheck {
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : WindowT {
        private bool isMatchAtCCD = false;
        private int isMatchAtCCDTime = 0;

        private bool isMatchAtRobot = false;
        private int isMatchAtRobotTime = 0;
        MainViewModel vm = new MainViewModel();

        //ConcurrentQueue<SeqNGData> queueCCDFromPLC = new ConcurrentQueue<SeqNGData>();
        public MainWindow() {
            //var data= ArrayToSeq(-25536, 0);
            InitializeComponent();
            this.DataContext = vm;
            this.Loaded += MainWindow_Loaded;
        }

        void UIInvoke(Action action) {
            DispatcherHelper.CheckBeginInvokeOnUI(() => {
                action?.Invoke();
            });
        }
        LocalDBHelper localDBHelper = new LocalDBHelper("tray_data".AsFullName());
        private async void MainWindow_Loaded(object sender, RoutedEventArgs e) {
            InitCfgErrorWarn();
            string error = "";
            var ret = await Run(() => {
                if (!localDBHelper.InitDir()) {
                    error = $"初始化目录异常! :{localDBHelper.SaveDir}";
                    return false;
                }

                if (!InitDevice(ref error)) {
                    return false;
                }

                if (!InitPLC(ref error)) {
                    return false;
                }
                initTimer();
                return true;
            });
            if (ret == false) {
                MessageBox.Show($"初始化失败 {error}");
                return;
            }

            StartQueueMonitor_CCD();
            StartQueueMonitor_Robot();


        }
        private DeviceCreator m_DeviceHelper = new DeviceCreator(AppData.ini_device);
        //private bool InitDevice(ref string error) {
        //    if (!m_DeviceHelper.Init(AppDevice.com_scaner2, ref error)
        //        ) {
        //        return false;
        //    }
        //    return true;
        //}

        private bool InitDevice(ref string error) {
            if (!m_DeviceHelper.Init(AppDevice.com_scaner1, ref error) ||
                !m_DeviceHelper.Init(AppDevice.com_scaner2, ref error)
                ) {
                return false;
            }
            return true;
        }


        bool initLotTblData(string lotId) {
            try {
                if (lotId.Null()) {
                    return false;
                }
                using (var dx = DBHelper.CreateConnect()) {
                    List<string> tbl = new List<string>() {
                    nameof(tbl_battery_scan),
                    nameof(tbl_battery_ccd),
                    nameof(tbl_battery_robot),
                    nameof(tbl_battery_pre_charge),
                    nameof(tbl_charge_data),
                };
                    foreach (var x in tbl) {
                        {
                            string sql = $"CREATE TABLE IF NOT EXISTS {lotId}_{x} LIKE {x};";
                            dx.SqlQuery<int>(sql);
                        }

                    }
                }

                return true;
            }
            catch (Exception ex) {
                AddUILog($"创建表异常! :{ex.ExceptionInfo()}");
                return false;
            }
        }
        AutoResetEvent autoResetEvent_ccd = new AutoResetEvent(false);
        AutoResetEvent autoResetEvent_robot = new AutoResetEvent(false);

        Thread thread_ccd = null;
        Thread thread_robot = null;

        public bool IsRunning { get; set; } = true;

        DataAtCCD dataAtCCD = null;
        PLCDataAtRobot dataAtRobot = null;
        private object lock_ccd_plc = new object();
        private object lock_robot_plc = new object();
        private object lock_scan = new object();
        private object lock_ccd = new object();
        private object lock_robot = new object();

        private void DoPlc_CCD_Pri() {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2CCD(msg, level); });
            lock (lock_ccd_plc) {
                if (isClearing) {
                    LogAction(log, $"清理中...不处理");
                    return;
                }
                if (BatteryDataManager.queue_fromPlc_ccd.TryDequeue(out var data)) {
                    dataAtCCD = data;
                    while (IsRunning) {
                        Thread.Sleep(10);
                        if (isClearing) {
                            LogAction(log, $"清理中...不处理");
                            return;
                        }

                        if ((DateTime.Now - data.Time).TotalSeconds < AppIniData.CCDDelaySecond) {
                            autoResetEvent_ccd.WaitOne(10);
                            continue;
                        }
                        bool hasError = false;
                        var seqNos = data.seqNos;
                        var ngFlags = data.ngFlags;

                        #region 保存到目录
                        try {
                            string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                            DirectoryHelper.CheckDirectory(dir);
                            string saveFileName = Path.Combine(dir, $"_ccd_battery.csv");
                            List<List<string>> listData = new List<List<string>>();
                            for (int i = 0; i < seqNos.Count; i++) {
                                Log.I($"ccd plcdata: {i + 1},seq:{seqNos[i]} ngFlag:{ngFlags[i]}");
                                if (i == 0) {
                                    listData.Add(new List<string> { seqNos[i].ToString(), ngFlags[i].ToString(), DateTime.Now.AsString() });
                                }
                                else {
                                    listData.Add(new List<string> { seqNos[i].ToString(), ngFlags[i].ToString() });
                                }
                            }
                            ExcelHelper.AppendToTXTByCache(saveFileName, listData);
                        }
                        catch (Exception ex) {
                            Log.E("save file error", ex);
                            LogAction(log, $"保存托盘数据到文件异常", EnumLogLevel.Error);
                        }
                        #endregion

                        if (!isMatchAtCCD) {
                            LogAction(log, $"待匹配模式");
                            isMatchAtCCDTime++;
                            if (isMatchAtCCDTime > 10) {
                                LogAction(log, $"待匹配模式超过限制次数!", EnumLogLevel.Error);
                            }
                        }
                        else {
                            LogAction(log, $"精准顺序匹配模式");
                        }
                        var listSame = FindDuplicates(seqNos);
                        if (listSame.Count > 0) {
                            LogAction(log, $"重复序列号! {string.Join(",", listSame)}", EnumLogLevel.Error);
                            hasError = true;
                            {
                                var list_same_save = new List<List<string>>();
                                foreach (var item in listSame) {
                                    LogAction(log, $"{item}", EnumLogLevel.Warning);
                                    if (list_same_save.Count == 0) {
                                        list_same_save.Add(new List<string> { item.ToString(), DateTime.Now.AsString() });
                                    }
                                    else {
                                        list_same_save.Add(new List<string> { item.ToString() });
                                    }
                                }
                                #region 保存重码
                                if (list_same_save.Count > 0) {
                                    try {
                                        string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                                        DirectoryHelper.CheckDirectory(dir);
                                        string saveFileName = Path.Combine(dir, $"_same_ccd.csv");
                                        ExcelHelper.AppendToTXTByCache(saveFileName, list_same_save);
                                    }
                                    catch (Exception ex) {
                                        Log.E("save same_ccd file error", ex);
                                        LogAction(log, $"保存 same_ccd 文件异常", EnumLogLevel.Error);
                                    }
                                }
                                #endregion
                            }
                        }
                        else {
                            for (int i = 0; i < seqNos.Count; i++) {
                                var seqNo = seqNos[i];
                                var ngFlag = ngFlags[i];
                                Log.I($"ccd plcdata: {i + 1},seq:{seqNo} ngFlag:{ngFlag}");
                                {
                                    //考虑下从中间开始的,怎么接上来
                                    if (!isMatchAtCCD) {
                                        Log.I($"ccd {i + 1},seq:{seqNo} ngFlag:{ngFlag} 当前是待匹配状态");
                                        if (preBatteryAtScan != null && preBatteryAtScan.ng_flag == 0 && preBatteryAtScan.battery_seq != 0) {
                                            Log.I($"ccd 尝试匹配上次剩余:{preBatteryAtScan.battery_seq}");
                                            if (preBatteryAtScan.battery_seq == seqNo) {
                                                //刚好匹配上
                                                LogAction(log, $"{i + 1},seq:{seqNo} 与scan匹配成功", EnumLogLevel.Scuess);
                                                isMatchAtCCD = true;
                                                if (ngFlag == 0)//没有ng加入正常队列
                                                {
                                                    BatteryDataManager.queue_data_at_scan_ccd.Enqueue(new BatteryDataAtScan() { battery_seq = seqNo });// 从扫码里面把ng的排出
                                                    LogAction(log, $"{i + 1},seq:{seqNo} 进ccd队列");
                                                }
                                                continue;
                                            }
                                            else if (preBatteryAtScan.battery_seq > seqNo) //扫码的比ccd的大
                                            {
                                                //说明当前的ccd的不进行检测
                                                Log.I($"{i + 1}, {seqNo} 比scan:{preBatteryAtScan.battery_seq}小");
                                                continue;
                                            }
                                            Log.I($"{i + 1}, {seqNo} 比 scan:{preBatteryAtScan.battery_seq}大,尝试从队列里面找");
                                            //扫码的比CCD小就得考虑从扫码队列里面继续取出来了 ,这个时候缓存的 preBatteryAtScan 就没用了
                                            preBatteryAtScan = null;
                                        }
                                        Log.I($"ccd 上次缓存没找到匹配,尝试从scan列队找");
                                        BatteryDataAtScan currentBattery = null;
                                        while (IsRunning) {
                                            if (isClearing) {
                                                LogAction(log, $"清理中...不处理");
                                                return;
                                            }
                                            if (!BatteryDataManager.queue_data_at_scan.TryDequeue(out currentBattery)) {
                                                LogAction(log, $"{i + 1},seq:{seqNo}  匹配异常,scan队列没数据", EnumLogLevel.Error);
                                                break; //暂时没有数据 等下次的
                                            }
                                            Log.I($"ccd 从scan队列取出来 :{currentBattery.battery_seq}");
                                            if (currentBattery.ng_flag == 1 || currentBattery.battery_seq == 0) { //因为扫码ng的也过来了,这个ccd会+1
                                                LogAction(log, $"{i + 1},seq:{seqNo} 跳过匹配,scan ng:{currentBattery.battery_seq}", EnumLogLevel.Warning);
                                                break;
                                            }
                                            if (currentBattery.battery_seq == seqNo) {
                                                LogAction(log, $"{i + 1},seq:{seqNo} 匹配成功", EnumLogLevel.Scuess);  //刚好匹配上
                                                isMatchAtCCD = true;
                                                if (ngFlag == 0)//没有ng加入正常队列
                                                {
                                                    LogAction(log, $"{i + 1},seq:{seqNo} 进ccd队列");
                                                    BatteryDataManager.queue_data_at_scan_ccd.Enqueue(new BatteryDataAtScan() { battery_seq = seqNo });// 从扫码里面把ng的排出 
                                                }
                                                break;
                                            }
                                            else if (currentBattery.battery_seq > seqNo) //扫码的比ccd的大
                                            {
                                                Log.I($"ccd:{seqNo} 比scan :{currentBattery.battery_seq}小,跳过");
                                                preBatteryAtScan = currentBattery;
                                                break;
                                            }
                                            else {
                                                //这里不做处理了,因为从队列找到的小,所以继续下个while从队列里面找
                                            }
                                        }
                                    }
                                    else {
                                        Log.I($"ccd {i + 1},seq:{seqNo} ngFlag:{ngFlag} 当前是精准顺序匹配模式");
                                        Log.I($"ccd 开始从scan队列取数据");
                                        var dt = DateTime.Now;
                                        while (IsRunning) {
                                            if (isClearing) {
                                                LogAction(log, $"清理中...不处理");
                                                return;
                                            }
                                            if (BatteryDataManager.queue_data_at_scan.Count > 0) {
                                                break;
                                            }
                                            if ((DateTime.Now - dt).TotalSeconds < AppIniData.CCDWaitOuttime) {
                                                LogAction(log, $"scan队列无数据,等会会", EnumLogLevel.Warning);
                                                Thread.Sleep(1000);
                                                continue;
                                            }
                                        }

                                        if (BatteryDataManager.queue_data_at_scan.TryDequeue(out var battery_at_scan)) {
                                            Log.I($"ccd 从scan取出来 {battery_at_scan.battery_seq},ngflag:{battery_at_scan.ng_flag} ");
                                            if (battery_at_scan.ng_flag == 1 || battery_at_scan.battery_seq == 0) {
                                                //这个是扫码NG的 ng 的也排走了
                                                LogAction(log, $"{i + 1},seq:{seqNo} scan:{battery_at_scan.battery_seq} ng,当前seq不做校对");
                                            }
                                            else {
                                                if (battery_at_scan.battery_seq == seqNo) {//匹配到了
                                                    Log.I($"ccd {i + 1},seq:{seqNo} 在scan中匹配到,ngFlag:{ngFlag}");
                                                    if (ngFlag == 0)//没有ng加入正常队列
                                                    {
                                                        LogAction(log, $"{i + 1},seq:{seqNo} 进ccd队列 :{(battery_at_scan.ng_flag == 1 ? "scan ng" : "")}");
                                                        BatteryDataManager.queue_data_at_scan_ccd.Enqueue(new BatteryDataAtScan() { battery_seq = seqNo });// 从扫码里面把ng的排出
                                                    }
                                                    continue;
                                                }
                                                else {
                                                    hasError = true;
                                                    LogAction(log, $"{i + 1},seq:{seqNo} 不匹配 :Scan列表为:{battery_at_scan.battery_seq}", EnumLogLevel.Error);
                                                }
                                            }
                                        }
                                        else {
                                            hasError = true;
                                            LogAction(log, $"{i + 1},Seq:{seqNo} 匹配异常 :scan队列无数据", EnumLogLevel.Error);
                                        }
                                    }
                                }
                            }
                        }

                        if (hasError) {
                            if (AppIniData.IsErrorOnCCD) {
                                if (clearFlag == EnumClearFlag.正常) {
                                    SendPLC1(EnumPLCWrite.S_2_2_CCD后电池序列号报警, 1);
                                    clearFlag = EnumClearFlag.清料中;
                                }
                                else {
                                    LogAction(log, $"清料状态,跳过报警", EnumLogLevel.Error);
                                }
                            }
                            else if (AppIniData.IsWarningOnCCD) {
                                SendPLC1(EnumPLCWrite.S_2_1_CCD后电池序列号警告, 1);
                            }
                        }
                        break;
                    }
                }
            }
        }
        void StartQueueMonitor_CCD() {
            thread_ccd = new Thread(() => {
                while (IsRunning) {
                    DoPlc_CCD_Pri();
                    autoResetEvent_robot.WaitOne(10);
                }

            }) { IsBackground = true };
            thread_ccd.Start();
        }
        SeqDataManager seq_jump = new SeqDataManager();
        SeqDataManager seq_same = new SeqDataManager();

        private void DoPLC_Robot_Pri() {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2Robot(msg, level); });
            lock (lock_robot_plc) {
                if (isClearing) {
                    LogAction(log, $"清理中...不处理");
                    return;
                }
                if (BatteryDataManager.queue_fromPlc_robot.TryDequeue(out var data)) {
                    dataAtRobot = data;
                    while (IsRunning) {
                        Thread.Sleep(10);
                        if (isClearing) {
                            LogAction(log, $"清理中...不处理");
                            return;
                        }
                        if ((DateTime.Now - data.Time).TotalSeconds < AppIniData.RobotDelaySecond) {
                            autoResetEvent_robot.WaitOne(10);
                            continue;
                        }

                        if (!isMatchAtRobot) {
                            LogAction(log, $"待匹配模式");
                        }
                        else {
                            LogAction(log, $"精准顺序匹配模式");
                        }

                        bool hasError = false;
                        var seqNos = data.seqNos;
                        var tray_code = data.tray_code;
                        Log.I($"robot plcdata: tray_code {tray_code}");


                        TrayData trayData = new TrayData() {
                            tray_code = tray_code,
                            cells = new List<int>(),
                            create_time = DateTime.Now
                        };
                        for (int i = 0; i < seqNos.Count; i++) {
                            Log.I($"robot plcdata: {i + 1},seq:{seqNos[i]}");
                            var seqNo = seqNos[i];
                            trayData.cells.Add(seqNo);
                        }
                        //要不要判断重复
                        var listSame = FindDuplicates(seqNos);
                        if (listSame.Count > 0) {
                            LogAction(log, $"重复序列号! {string.Join(",", listSame)}", EnumLogLevel.Error);
                            hasError = true;
                            trayData.is_same = true;
                            {
                                var list_same_save = new List<List<string>>();
                                foreach (var item in listSame) {
                                    LogAction(log, $"{item}", EnumLogLevel.Warning);
                                    if (list_same_save.Count == 0) {
                                        list_same_save.Add(new List<string> { item.ToString(),tray_code, DateTime.Now.AsString() });
                                    }
                                    else {
                                        list_same_save.Add(new List<string> { item.ToString() });
                                    }
                                }
                                #region 保存重码
                                if (list_same_save.Count > 0) {
                                    try {
                                        string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                                        DirectoryHelper.CheckDirectory(dir);
                                        string saveFileName = Path.Combine(dir, $"_same_robot.csv");
                                        ExcelHelper.AppendToTXTByCache(saveFileName, list_same_save);
                                    }
                                    catch (Exception ex) {
                                        Log.E("save same_ccd file error", ex);
                                        LogAction(log, $"保存 same_robot 文件异常", EnumLogLevel.Error);
                                    }
                                }
                                #endregion
                            }
                        }
                        else {
                            List<int> list = new List<int>();
                            for (int i = 0; i < seqNos.Count; i++) {
                                var seqNo = seqNos[i];
                                if (seqNo == 0) {
                                    LogAction(log, $"{i + 1},seq:{seqNo} 清料");
                                }
                                else {
                                    if (!isMatchAtRobot) {
                                        if (preBatteryAtCCD != null) {
                                            Log.I($"robot {i + 1},seq:{seqNo} 尝试匹配缓存 :{preBatteryAtCCD.battery_seq}");
                                            if (preBatteryAtCCD.battery_seq == seqNo) {
                                                //刚好匹配上
                                                LogAction(log, $"{i + 1},seq:{seqNo} 已经匹配上", EnumLogLevel.Scuess);
                                                isMatchAtRobot = true;
                                                continue;
                                            }
                                            else if (preBatteryAtCCD.battery_seq > seqNo) //扫码的比ccd的大
                                            {
                                                //说明当前的ccd的不进行检测
                                                LogAction(log, $"{i + 1},seq:{seqNo} 比ccd:{preBatteryAtCCD.battery_seq}小,匹配不了");
                                                continue;
                                            }
                                            Log.I($"{i + 1},seq:{seqNo} 比ccd:{preBatteryAtCCD.battery_seq}大 当前,尝试从队列里面找");
                                            //扫码的比CCD小就得考虑从扫码队列里面继续取出来了 ,这个时候缓存的 preBatteryAtCCD 就没用了
                                            preBatteryAtCCD = null;
                                        }

                                        while (IsRunning) {
                                            if (isClearing) {
                                                LogAction(log, $"清理中...不处理");
                                                return;
                                            }
                                            BatteryDataAtScan currentBattery = null;
                                            if (!BatteryDataManager.queue_data_at_scan_ccd.TryDequeue(out currentBattery)) {
                                                LogAction(log, $"{i + 1},seq:{seqNo} ccd队列为空,无法匹配");
                                                break; //暂时没有数据 等下次的
                                            }
                                            Log.I($"robot 从ccd队列取出来 {currentBattery.battery_seq},尝试匹配");
                                            if (currentBattery.battery_seq == seqNo) {
                                                LogAction(log, $"{i + 1},seq:{seqNo} 完成匹配", EnumLogLevel.Scuess);  //刚好匹配上
                                                isMatchAtRobot = true;
                                                break;
                                            }
                                            else if (currentBattery.battery_seq > seqNo) //扫码的比ccd的大
                                            {
                                                LogAction(log, $"{i + 1},seq:{seqNo} 比ccd :{currentBattery.battery_seq}小,匹配不了");
                                                preBatteryAtCCD = currentBattery;
                                                break;
                                            }
                                            else {
                                                //这里不做处理了,因为从队列找到的小,所以继续下个while从队列里面找
                                                Log.I($"robot 当前ccd的没有匹配 继续再匹配下一个ccd");
                                            }
                                        }
                                    }
                                    else {
                                        var dt = DateTime.Now;
                                        while (IsRunning) {
                                            if (isClearing) {
                                                LogAction(log, $"清理中...不处理");
                                                return;
                                            }
                                            if (BatteryDataManager.queue_data_at_scan_ccd.Count > 0) {
                                                break;
                                            }
                                            if ((DateTime.Now - dt).TotalSeconds < AppIniData.RobotWaitOuttime) {
                                                AddLog2CCD($"ccd 队列无数据,等会会", EnumLogLevel.Warning);
                                                Thread.Sleep(1000);
                                                continue;
                                            }
                                        }
                                        if (BatteryDataManager.queue_data_at_scan_ccd.TryDequeue(out var battery_at_ccd)) {
                                            if (battery_at_ccd.battery_seq == seqNo) //匹配到了
                                            {
                                                LogAction(log, $"{i + 1},seq:{seqNo} 在ccd中匹配到");
                                                continue;
                                            }
                                            else {
                                                hasError = true;
                                                LogAction(log, $"{i + 1},seq:{seqNo} 不匹配,ccd为:{battery_at_ccd.battery_seq}", EnumLogLevel.Error);
                                            }
                                        }
                                        else {
                                            LogAction(log, $"{i + 1},seq:{seqNo} 在ccd中未匹配到,ccd无数据", EnumLogLevel.Error);
                                            hasError = true;
                                        }
                                    }
                                }
                            }
                        }

                        #region 检查重码 
                        for (int i = 0; i < trayData.cells.Count; i++) {
                            var seqNo = trayData.cells[i];
                            if (seq_same.CheckAndRemoveSeq(seqNo)) {
                                trayData.is_same = true;
                                hasError = true;
                                LogAction(log, $"{i + 1},seq:{seqNo} tray:{tray_code},记录重码托盘", EnumLogLevel.Error);
                            }
                        }
                        #endregion

                        #region 检查跳码 
                        for (int i = 0; i < trayData.cells.Count; i++) {
                            var seqNo = trayData.cells[i];
                            if (seq_jump.CheckAndRemoveSeq(seqNo)) {
                                trayData.is_jump = true;
                                hasError = true;
                                LogAction(log, $"{i + 1},seq:{seqNo} tray:{tray_code},记录跳码托盘", EnumLogLevel.Error);
                                break;//记录一个就行了,没必要全纪录
                            }
                        }
                        #endregion

                        trayData.iserror_at_robot = hasError;

                        if (clearFlag != EnumClearFlag.正常) {
                            trayData.iserror_at_robot = true;
                            LogAction(log, $"清料状态,自动标记", EnumLogLevel.Error);
                        }
                        //保存本地

                        #region 保存托盘
                        try {
                            string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                            DirectoryHelper.CheckDirectory(dir);
                            string saveFileName = Path.Combine(dir, $"{trayData.tray_code}_{trayData.create_time.AsFileNameString()}.csv");
                            List<List<string>> listData = new List<List<string>>();
                            listData.Add(new List<string> { "trayID", "time" });
                            listData.Add(new List<string> { trayData.tray_code, trayData.create_time.AsString() });

                            for (int i = 0; i < trayData.cells.Count; i++) {
                                listData.Add(new List<string> { (i + 1).ToString(), trayData.cells[i].ToString() });
                            };
                            ExcelHelper.AppendToTXT(saveFileName, listData);
                        }
                        catch (Exception ex) {
                            Log.E("save tray file error", ex);
                            LogAction(log, $"保存托盘数据到文件异常", EnumLogLevel.Error);
                        }
                        #endregion

                        localDBHelper.Save2DB(AppIniData.LotId, trayData, out string info);
                        if (info.NotNull()) {
                            LogAction(log, $"保存托盘数据到DB异常 :{info}", EnumLogLevel.Error);
                        }

                        if (hasError) {
                            if (AppIniData.IsErrorOnRobot) {
                                if (clearFlag == EnumClearFlag.正常) {
                                    LogAction(log, $"发送 报警", EnumLogLevel.Warning);
                                    SendPLC1(EnumPLCWrite.S_1_2_扫码枪后报警, 1);
                                    clearFlag = EnumClearFlag.清料中;//标记清料中
                                }
                                else {
                                    LogAction(log, $"清料状态,跳过报警", EnumLogLevel.Error);
                                }
                            }
                            else if (AppIniData.IsWarningOnRobot) {
                                LogAction(log, $"发送 警告", EnumLogLevel.Warning);
                                SendPLC2(EnumPLCWrite.S_3_1_机械手电池序列号警告, 1);
                            }
                        }
                        break;
                    }
                }
            }
        }

        void StartQueueMonitor_Robot() {
            thread_robot = new Thread(() => {
                while (IsRunning) {
                    DoPLC_Robot_Pri();
                    autoResetEvent_robot.WaitOne(10);
                }

            }) { IsBackground = true };
            thread_robot.Start();
        }

        //void StartQueueMonitor_Robot() {
        //    thread3 = new Thread(() => {
        //        using (var dx = DBHelper.CreateConnect()) {
        //            while (IsRunning) {
        //                if (BatteryDataManager.queue_data_at_robot.TryDequeue(out var data)) {
        //                    var tablename = $"{nameof(tbl_battery_robot)}_{data.OrderNo}";

        //                    foreach (var item in data.ListBatterySeq) {
        //                        dx.Insert(new tbl_battery_robot {
        //                            battery_seq = item,
        //                            create_time = DateTime.Now,
        //                            robot_pos = data.RobotPos,
        //                            tray_code = data.RobotTraycode
        //                        }, tablename);
        //                    }

        //                }
        //                else {
        //                    autoResetEvent3.WaitOne(10);
        //                }
        //            }
        //        }

        //    }) { IsBackground = true };
        //    thread3.Start();
        //}
        //void StartQueueMonitor_PreCharge() {
        //    thread4 = new Thread(() => {
        //        using (var dx = DBHelper.CreateConnect()) {
        //            while (IsRunning) {
        //                if (BatteryDataManager.queue_data_at_preCharge.TryDequeue(out var data)) {
        //                    var tablename = $"{nameof(tbl_battery_pre_charge)}_{data.OrderNo}";

        //                    foreach (var item in data.ListBatteryId) {
        //                        dx.Insert(new tbl_battery_pre_charge {
        //                            battery_seq = item,
        //                            create_time = DateTime.Now,
        //                            tray_code = data.Traycode
        //                        }, tablename);
        //                    }

        //                }
        //                else {
        //                    autoResetEvent4.WaitOne(10);
        //                }
        //            }
        //        }

        //    }) { IsBackground = true };
        //    thread4.Start();
        //}


        private bool InitPLC(ref string error) {
            Log.I($"初始化PLC");
            AppDevice.plc1 = new MXConnector(AppIniData.StationNo1, "PLC1");
            AppDevice.plc2 = new MXConnector(AppIniData.StationNo2, "PLC2");
            var ret = AppCreator.m_PLCCreator1.InitPLC(AppDevice.plc1, Opc_OnDataChange, ref error)
                && AppCreator.m_PLCCreator2.InitPLC(AppDevice.plc2, Opc_OnDataChange, ref error)
                ;
            return ret;
        }

        private void AddUILog(String str, EnumLogLevel enumLog = EnumLogLevel.Message) {
            UIInvoke(() => {
                vm.ListLog.Add(new LogModel { Msg = str, Brush = (enumLog == EnumLogLevel.Error ? Brushes.Red : Brushes.Transparent) });
                if (vm.ListLog.Count > 30) {
                    vm.ListLog.RemoveAt(0);
                }
            });
            Log.I(str);
        }
        private void Opc_OnDataChange(string groupName, int index, object value) {
            System.Diagnostics.Debug.Print($"当前线程-Opc_OnDataChange-{Thread.CurrentThread.ManagedThreadId}");
            if (value == null)
                return;
            if (value is short isSignal && isSignal == 1) {

            }
            else {
                return;
            }
            if (groupName == nameof(EnumPLCTrigger)) {
                EnumPLCTrigger tagIndex = (EnumPLCTrigger)index;

                switch (tagIndex) {

                    case EnumPLCTrigger.T_1_扫码枪后抓电池条码:
                        try {
                            AddUILog($"---->Scan开始 {value}");
                            lock (lock_scan) {
                                if (isClearing) {
                                    AddLog2Scan($"清理中...不处理");
                                    return;
                                }
                                DoPLC_AtScan();
                            }
                            //DoPLC_OCV(value);
                            AddUILog($"---->Scan 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"Scan开始--e:{ex.ExceptionInfo()}");
                        }

                        break;

                    case EnumPLCTrigger.T_2_CCD后电池序列号触发:
                        try {
                            AddUILog($"---->CCD后 开始 {value}");

                            lock (lock_ccd) {
                                if (isClearing) {
                                    AddLog2CCD($"清理中...不处理");
                                    return;
                                }
                                DoPLC_AtCCD();
                            }
                            //DoPLC_OCV(value);
                            AddUILog($"---->CCD后 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"CCD后--e:{ex.ExceptionInfo()}");
                        }

                        break;


                    case EnumPLCTrigger.T_3_机械手电池序列号触发:
                        try {
                            AddUILog($"---->T_3_机械手触发 开始 {value}");
                            Thread.Sleep(2000);

                            lock (lock_robot) {
                                if (isClearing) {
                                    AddLog2Robot($"清理中...不处理");
                                    return;
                                }
                                DoPLC_AtRobot(value);
                            }
                            AddUILog($"---->T_3_机械手触发 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"T_3_机械手电池序列号触发--e:{ex.ExceptionInfo()}");
                        }
                        break;

                    case EnumPLCTrigger.T_4_预充电后到位触发_1:
                        try {
                            AddUILog($"---->PreCharge1 开始 {value}");
                            DoPLC_BeforePreCharge_At1(value);
                            AddUILog($"---->PreCharge2 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"T_4_预充电后到位触发_1--e:{ex.ExceptionInfo()}");
                        }
                        break;
                    case EnumPLCTrigger.T_5_预充电后到位触发_2:
                        try {
                            AddUILog($"---->PreCharge2 开始 {value}");
                            DoPLC_BeforePreCharge_At2(value);
                            //DoPLC_OCV(value);
                            AddUILog($"---->PreCharge2 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"PreCharge2--e:{ex.ExceptionInfo()}");
                        }

                        break;

                    case EnumPLCTrigger.T_6_读取批次触发:
                        try {
                            AddUILog($"---->读取批次 {value}");
                            DoPLC_ReadLotData(value);
                            //DoPLC_OCV(value);
                            AddUILog($"---->读取批次 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"T_6_读取批次触发--e:{ex.ExceptionInfo()}");
                        }

                        break;

                    case EnumPLCTrigger.T_7_预充电后到位触发1:
                        try {
                            AddUILog($"---->EndCharge1 开始 {value}");
                            DoPLC_EndCharge_At1(value);
                            //DoPLC_OCV(value);
                            AddUILog($"---->EndCharge1 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"EndCharge1--error:{ex.ExceptionInfo()}");
                        }

                        break;

                    case EnumPLCTrigger.T_8_预充电后到位触发2:
                        try {
                            AddUILog($"---->EndCharge2 开始 {value}");
                            DoPLC_EndCharge_At2(value);
                            //DoPLC_OCV(value);
                            AddUILog($"---->EndCharge2 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"EndCharge2--error:{ex.ExceptionInfo()}");
                        }

                        break;
                    case EnumPLCTrigger.T_10_清线完成:
                        try {
                            AddUILog($"---->ClearLine 开始 {value}");
                            DOPLC_ClearLine(value);
                            //DoPLC_OCV(value);
                            AddUILog($"---->ClearLine 结束 {value}");
                        }
                        catch (Exception ex) {
                            Log.I($"ClearLine--error:{ex.ExceptionInfo()}");
                        }

                        break;


                    default:
                        break;
                }
            }
        }

        private void DOPLC_ClearLine(object value) {
            ClearAll();
            SendPLC2(EnumPLCWrite.S_10_清线处理完成, 1);//清线完成了
            AddLog2Scan("清料完成");
            clearFlag = EnumClearFlag.清料完成;

        }

        private bool SendPLC1(EnumPLCWrite enumPLCWrite, short val, bool log = true) {
            var ret = AppDevice.plc1.Write(nameof(EnumPLCWrite), (int)enumPLCWrite, val);
            if (log) AddUILog($"---->SendPLC :{enumPLCWrite} {val}");
            return ret;
        }

        private bool ReadPLC1(EnumPLCRead enumPlc, out object val) {
            var ret = AppDevice.plc1.Read(nameof(EnumPLCRead), (int)enumPlc, out val);
            AddUILog($"---->ReadPLC :{enumPlc} {val}");
            return ret;
        }

        private bool SendPLC2(EnumPLCWrite enumPLCWrite, short val, bool log = true) {
            var ret = AppDevice.plc2.Write(nameof(EnumPLCWrite), (int)enumPLCWrite, val);
            if (log) AddUILog($"---->SendPLC :{enumPLCWrite} {val}");
            return ret;
        }

        private bool ReadPLC2(EnumPLCRead enumPlc, out object val) {
            var ret = AppDevice.plc2.Read(nameof(EnumPLCRead), (int)enumPlc, out val);
            AddUILog($"---->ReadPLC :{enumPlc} {val}");
            return ret;
        }

        private string ArrayToString(short[] array) {

            List<byte> bytes = new List<byte>();
            foreach (var item in array) {
                var _byte = BitConverter.GetBytes(item);
                bytes.Add(_byte[0]);
                bytes.Add(_byte[1]);
            }
            return Encoding.ASCII.GetString(bytes.ToArray()).Replace("\0", "").Replace("\r\n", "").Replace("\r", "").Replace("\n", "");
        }
        private int[] ArrayToSeqs(short[] array) {

            List<int> data = new List<int>();
            for (int i = 0; i < array.Length; i += 3) {
                data.Add(ArrayToSeq(array[i], array[i + 1]));
            }
            return data.ToArray();
        }
        private int ArrayToSeq(short a, short b) {
            return (ushort)a + ((ushort)b << 16);
        }
        private int ArrayToInt(IEnumerable<int> array) {
            return 0;
        }

        ///// <summary>
        ///// 如果没有重复的 返回true
        ///// </summary>
        ///// <param name="seqNos"></param>
        ///// <returns></returns>
        //private bool CheckDistinct(List<int> seqNos) {
        //    return seqNos.Count == seqNos.Distinct().Count();
        //}

        List<int> FindDuplicates(List<int> numbers) {
            var list = numbers.GroupBy(n => n)
                          .Where(g => g.Count() > 1)
                          .Select(g => g.Key)
                          .ToList();
            list.Remove(0);
            return list;
        }
        /// <summary>
        /// 是否开始监测条码
        /// </summary>
        private bool isCheckJump = false;
        private int checkJumpSeq = 0;

        private void LogAction(Action<string, EnumLogLevel> log, string msg, EnumLogLevel logLevel = EnumLogLevel.Message) {
            if (log != null) {
                log.Invoke(msg, logLevel);
            }
        }

        private void DoPLC_AtScan() {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2Scan(msg, level); });
            LogAction(log, $"start--");
            if (clearFlag == EnumClearFlag.清料完成) {
                clearFlag = EnumClearFlag.正常;
                LogAction(log, $"正常,清料彻底完成", EnumLogLevel.Scuess);
            }
            if (clearFlag != EnumClearFlag.正常) {
                LogAction(log, $"清料扫码,只是提示 {clearFlag}", EnumLogLevel.Error);
            }

            bool hasError = false;
            bool hasWarn = false;
            if (!ReadPLC1(EnumPLCRead.R_1_扫码位电池数据, out var obj)) {
                LogAction(log, $"读取 R_1_扫码位电池数据 失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (hasError == false) {
                if (obj is short[] listSeq) {
                    List<string> batterys = new List<string>();
                    List<int> seqNos = new List<int>();
                    List<int> ngFlags = new List<int>();

                    List<int> seqCheck = new List<int>();
                    for (int i = listSeq.Length - 20; i >= 0; i -= 20) {
                        var batteryId = ArrayToString(listSeq.Skip(i).Take(16).ToArray());
                        var seqNo = ArrayToSeq(listSeq[i + 16], listSeq[i + 17]);
                        var ngFlag = listSeq[i + 19];
                        //if (seqNo != 0) {
                        batterys.Add(batteryId);
                        seqNos.Add(seqNo);
                        ngFlags.Add(ngFlag);
                        if (ngFlag == 0 && seqNo != 0) {
                            //不ng 做重复检测
                            seqCheck.Add(seqNo);
                        }
                    }

                    #region 跳码监测
                    bool hasJump = false;
                    {
                        var list_jump_save = new List<List<string>>();
                        for (int i = 0; i < seqNos.Count; i++) {
                            Log.I($"scan plcdata: {i + 1},seq:{seqNos[i]},id:{batterys[i]} ngFlag:{ngFlags[i]}");
                            if (isCheckJump == false) {
                                if (seqNos[i] != 0 && ngFlags[i] != 1 && batterys[i] != "ERROR") {
                                    isCheckJump = true;
                                    checkJumpSeq = seqNos[i];
                                }
                            }
                            else {
                                if (seqNos[i] != 0 && ngFlags[i] != 1 && batterys[i] != "ERROR") {
                                    if (checkJumpSeq + 1 != seqNos[i]) {
                                        hasJump = true;
                                        hasError = true;
                                        isCheckJump = false;
                                        seq_jump.AddSeq(seqNos[i]);//记录 当前的 跳码 序列号
                                        LogAction(log, $"跳码监测 ERROR :应该{checkJumpSeq + 1}-实际{seqNos[i]}", EnumLogLevel.Error);
                                        if (list_jump_save.Count == 0) {
                                            list_jump_save.Add(new List<string> { (i + 1).ToString(), seqNos[i].ToString(), batterys[i], DateTime.Now.AsString() });
                                        }
                                        else {
                                            list_jump_save.Add(new List<string> { (i + 1).ToString(), seqNos[i].ToString(),batterys[i] });
                                        }
                                    }
                                    else {
                                        checkJumpSeq = checkJumpSeq + 1;
                                    }
                                }
                                else {
                                    checkJumpSeq = checkJumpSeq + 1;
                                }
                            }
                        }

                        #region 保存重码

                        if (list_jump_save.Count > 0) {
                            try {
                                string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                                DirectoryHelper.CheckDirectory(dir);
                                string saveFileName = Path.Combine(dir, $"_jump_scan.csv");
                                ExcelHelper.AppendToTXTByCache(saveFileName, list_jump_save);
                            }
                            catch (Exception ex) {
                                Log.E("save jump_scan file error", ex);
                                LogAction(log, $"保存 same_scan 文件异常", EnumLogLevel.Error);
                            }
                        }
                        #endregion
                    }
                    if (hasJump == false) {
                        LogAction(log, $"跳码监测 PASS", EnumLogLevel.Scuess);
                    }

                    #endregion
                    {
                        var listSame = FindDuplicates(seqCheck);
                        if (listSame.Count > 0) {
                            LogAction(log, $"重复序列号! {string.Join(",", listSame)}", EnumLogLevel.Warning);
                            hasWarn = true;
                            foreach (var item in listSame) {
                                seq_same.AddSeqs(listSame);
                            }
                        }
                    }
                    {
                        var listSame = FindDuplicates(seqNos); //检查所有的电池,不管ng
                        if (listSame.Count > 0) {
                            var list_same_save = new List<List<string>>();
                            LogAction(log, $"重复序列号!", EnumLogLevel.Warning);
                            foreach (var item in listSame) {
                                LogAction(log, $"{item}", EnumLogLevel.Warning);
                                if (list_same_save.Count == 0) {
                                    list_same_save.Add(new List<string> { item.ToString(), DateTime.Now.AsString() });
                                }
                                else {
                                    list_same_save.Add(new List<string> { item.ToString() });
                                }
                            }
                            #region 保存重码
                            if (list_same_save.Count > 0) {
                                try {
                                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                                    DirectoryHelper.CheckDirectory(dir);
                                    string saveFileName = Path.Combine(dir, $"_same_scan.csv");
                                    ExcelHelper.AppendToTXTByCache(saveFileName, list_same_save);
                                }
                                catch (Exception ex) {
                                    Log.E("save same_scan file error", ex);
                                    LogAction(log, $"保存 same_scan 文件异常", EnumLogLevel.Error);
                                }
                            }
                            #endregion
                        }
                    }

                    for (int i = 0; i < batterys.Count; i++) {
                        if (seqNos[i] != 0 && ngFlags[i] != 1 && batterys[i] != "ERROR") {
                            LogAction(log, $"{i + 1} {seqNos[i]} OK");
                        }
                        else {
                            LogAction(log, $"{i + 1} {seqNos[i]} NG flag:{ngFlags[i]} battery:{batterys[i]}", EnumLogLevel.Warning);
                        }
                    }

                    for (int i = 0; i < seqNos.Count; i++) {
                        Log.I($"{i + 1} {seqNos[i]} 进scan队列,ngFlag:{ngFlags[i]}");
                        BatteryDataManager.queue_data_at_scan.Enqueue(new BatteryDataAtScan {
                            battery_id = batterys[i],
                            battery_seq = seqNos[i],
                            work_time = DateTime.Now,
                            OrderNo = AppIniData.LotId,
                            ng_flag = ngFlags[i],
                        });
                    }

                    #region 保存到目录
                    try {
                        string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                        DirectoryHelper.CheckDirectory(dir);
                        string saveFileName = Path.Combine(dir, $"_scan_battery.csv");
                        List<List<string>> listData = new List<List<string>>();
                        for (int i = 0; i < seqNos.Count; i++) {
                            if (i == 0) {
                                listData.Add(new List<string> { seqNos[i].ToString(), batterys[i], ngFlags[i].ToString(), DateTime.Now.AsString() });
                            }
                            else {
                                listData.Add(new List<string> { seqNos[i].ToString(), batterys[i], ngFlags[i].ToString() });
                            }
                        }
                        ExcelHelper.AppendToTXTByCache(saveFileName, listData);
                    }
                    catch (Exception ex) {
                        Log.E("save file error", ex);
                        LogAction(log, $"保存托盘数据到文件异常", EnumLogLevel.Error);
                    }
                    #endregion
                }
            }
            if (hasError) {
                if (AppIniData.IsErrorOnScan) {
                    if (clearFlag == EnumClearFlag.正常) {
                        SendPLC1(EnumPLCWrite.S_1_2_扫码枪后报警, 1);
                        clearFlag = EnumClearFlag.清料中;//标记清料中
                    }
                    else {
                        LogAction(log, $"清料状态,跳过报警", EnumLogLevel.Error);
                    }
                }
            }
            if (hasWarn) {
                if (AppIniData.IsWarningOnScan) {
                    SendPLC1(EnumPLCWrite.S_1_1_扫码枪后警告, 1);
                }
            }
            LogAction(log, $"end--");
            //这里开始读取数据
            SendPLC1(EnumPLCWrite.S_1_扫码枪后抓电池条码完成, 1);
        }

        private BatteryDataAtScan preBatteryAtScan = null;
        private BatteryDataAtScan preBatteryAtCCD = null;
        private void DoPLC_AtCCD() {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2CCD(msg, level); });
            bool hasError = false;
            if (!ReadPLC1(EnumPLCRead.R_2_CCD后电池序列号数据, out var obj)) {
                LogAction(log, $"1工位读取电池序列号失败!", EnumLogLevel.Error);
                hasError = true;
            }
            else {
                if (obj is short[] listSeq) {
                    //开始判断 是否和前面的匹配
                    //这里做下次数判断 防止一直处于 isMatchAtCCD false 模式                    
                    var seqNos = new List<int>();
                    var ngFlags = new List<int>();
                    for (int i = listSeq.Length - 3; i >= 0; i -= 3) {
                        var seqNo = ArrayToSeq(listSeq[i], listSeq[i + 1]);
                        var ngFlag = listSeq[i + 2];
                        seqNos.Add(seqNo);
                        ngFlags.Add(ngFlag);
                    };

                    BatteryDataManager.queue_fromPlc_ccd.Enqueue(new DataAtCCD() {
                        ngFlags = ngFlags,
                        seqNos = seqNos,
                        Time = DateTime.Now
                    });

                }
            }
            if (hasError) {
                if (AppIniData.IsErrorOnCCD) {
                    SendPLC1(EnumPLCWrite.S_2_2_CCD后电池序列号报警, 1);
                }
                else if (AppIniData.IsWarningOnCCD) {
                    SendPLC1(EnumPLCWrite.S_2_1_CCD后电池序列号警告, 1);
                }
            }
            //这里开始读取数据
            SendPLC1(EnumPLCWrite.S_2_CCD后电池序列号完成, 1);
        }

        private LogModel CreateLog(string msg, EnumLogLevel level) {
            LogModel logModel = new LogModel() { Msg = $"{DateTime.Now.AsString("HH:mm:ss")} {msg}", Time = DateTime.Now.AsString("HH:mm:ss") };
            switch (level) {
                case EnumLogLevel.Message:
                    break;
                case EnumLogLevel.Warning:
                    logModel.Brush = Brushes.Yellow;
                    break;
                case EnumLogLevel.Error:
                    logModel.Brush = Brushes.OrangeRed;
                    break;
                case EnumLogLevel.Scuess:
                    logModel.Brush = Brushes.LightGreen;
                    break;
                default:
                    break;
            }
            return logModel;
        }

        private void AddLog2CCD(string msg, EnumLogLevel level = EnumLogLevel.Message) {

            if (level != EnumLogLevel.Error) {
                Log.I($"ccd:{msg}");
                UIInvoke(() => {
                    vm.ListLog_AtCCD.Add(CreateLog(msg, level));
                    if (vm.ListLog_AtCCD.Count > 50) {
                        vm.ListLog_AtCCD.RemoveAt(0);
                    }

                    list_log_at_ccd.ScrollToBottom();
                });
            }
            else {
                Log.I($"ccd:error{msg}");
                UIInvoke(() => {
                    vm.ListErrorLog_AtCCD.Add(CreateLog(msg, level));
                    if (vm.ListErrorLog_AtCCD.Count > 50) {
                        vm.ListErrorLog_AtCCD.RemoveAt(0);
                    }

                    list_error_at_ccd.ScrollToBottom();
                });

                try {
                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                    DirectoryHelper.CheckDirectory(dir);
                    string saveFileName = Path.Combine(dir, $"_error_ccd.csv");
                    ExcelHelper.AppendToTXTByCache(saveFileName, msg);
                }
                catch (Exception ex) {
                    Log.E("save _error_robot file error", ex);
                }
            }

        }

        private void AddLog2Scan(string msg, EnumLogLevel level = EnumLogLevel.Message) {
            if (level != EnumLogLevel.Error) {
                Log.I($"scan: {msg}");
                UIInvoke(() => {
                    vm.ListLog_AtScan.Add(CreateLog(msg, level));
                    if (vm.ListLog_AtScan.Count > 50) {
                        vm.ListLog_AtScan.RemoveAt(0);
                    }
                    list_log_at_scan.ScrollToBottom();
                });
            }
            else {
                Log.I($"scan:error {msg}");
                UIInvoke(() => {
                    vm.ListErrorLog_AtScan.Add(CreateLog(msg, level));
                    if (vm.ListErrorLog_AtScan.Count > 50) {
                        vm.ListErrorLog_AtScan.RemoveAt(0);
                    }
                    list_error_at_scan.ScrollToBottom();
                });

                try {
                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                    DirectoryHelper.CheckDirectory(dir);
                    string saveFileName = Path.Combine(dir, $"_error_scan.csv");
                    ExcelHelper.AppendToTXTByCache(saveFileName, msg);
                }
                catch (Exception ex) {
                    Log.E("save _error_robot file error", ex);
                }
            }


        }

        private void AddLog2Robot(string msg, EnumLogLevel level = EnumLogLevel.Message) {

            if (level != EnumLogLevel.Error) {
                Log.I($"robot: {msg}");
                UIInvoke(() => {
                    vm.ListLog_AtRobot.Add(CreateLog(msg, level));
                    if (vm.ListLog_AtRobot.Count > 50) {
                        vm.ListLog_AtRobot.RemoveAt(0);
                    }
                    list_log_at_robot.ScrollToBottom();
                });
            }
            else {
                Log.I($"robot:error {msg}");
                UIInvoke(() => {
                    vm.ListErrorLog_AtRobot.Add(CreateLog(msg, level));
                    if (vm.ListErrorLog_AtRobot.Count > 50) {
                        vm.ListErrorLog_AtRobot.RemoveAt(0);
                    }
                    list_error_at_robot.ScrollToBottom();
                });

                try {
                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                    DirectoryHelper.CheckDirectory(dir);
                    string saveFileName = Path.Combine(dir, $"_error_robot.csv");
                    ExcelHelper.AppendToTXTByCache(saveFileName, msg);
                }
                catch (Exception ex) {
                    Log.E("save _error_robot file error", ex); 
                }

            }
        }
        private void AddLog2PreCharge(string msg, EnumLogLevel level = EnumLogLevel.Message) {
            Log.I($"precharge1:{msg}");
            if (level != EnumLogLevel.Error) {
                UIInvoke(() => {
                    vm.ListLog_AtPreCharge.Add(CreateLog(msg, level));

                    if (vm.ListLog_AtPreCharge.Count > 50) {
                        vm.ListLog_AtPreCharge.RemoveAt(0);
                    }
                    list_log_at_precharge.ScrollToBottom();
                });
            }
            else {
                UIInvoke(() => {
                    vm.ListErrorLog_AtPreCharge.Add(CreateLog(msg, level));

                    if (vm.ListErrorLog_AtPreCharge.Count > 50) {
                        vm.ListErrorLog_AtPreCharge.RemoveAt(0);
                    }
                    list_error_at_precharge.ScrollToBottom();
                });

                try {
                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                    DirectoryHelper.CheckDirectory(dir);
                    string saveFileName = Path.Combine(dir, $"_error_precharge.csv");
                    ExcelHelper.AppendToTXTByCache(saveFileName, msg);
                }
                catch (Exception ex) {
                    Log.E("save _error_robot file error", ex);
                }
            }
        }
        private void AddLog2EndCharge(string msg, EnumLogLevel level = EnumLogLevel.Message) {
            Log.I($"endcharge1:{msg}");
            if (level != EnumLogLevel.Error) {
                UIInvoke(() => {
                    vm.ListLog_AtChargeEnd1.Add(CreateLog(msg, level));

                    if (vm.ListLog_AtChargeEnd1.Count > 50) {
                        vm.ListLog_AtChargeEnd1.RemoveAt(0);
                    }
                    list_log_at_endharge.ScrollToBottom();
                });
            }
            else {
                UIInvoke(() => {
                    vm.ListErrorLog_AtChargeEnd1.Add(CreateLog(msg, level));

                    if (vm.ListErrorLog_AtChargeEnd1.Count > 50) {
                        vm.ListErrorLog_AtChargeEnd1.RemoveAt(0);
                    }
                    list_error_at_endcharge.ScrollToBottom();
                });

                try {
                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                    DirectoryHelper.CheckDirectory(dir);
                    string saveFileName = Path.Combine(dir, $"_error_endcharge.csv");
                    ExcelHelper.AppendToTXTByCache(saveFileName, msg);
                }
                catch (Exception ex) {
                    Log.E("save _error_robot file error", ex);
                }
            }
        }
        private void AddLog2EndCharge2(string msg, EnumLogLevel level = EnumLogLevel.Message) {
            Log.I($"endcharge2:{msg}");
            if (level != EnumLogLevel.Error) {
                UIInvoke(() => {
                    vm.ListLog_AtChargeEnd2.Add(CreateLog(msg, level));

                    if (vm.ListLog_AtChargeEnd2.Count > 50) {
                        vm.ListLog_AtChargeEnd2.RemoveAt(0);
                    }
                    list_log_at_endcharge2.ScrollToBottom();
                });
            }
            else {
                UIInvoke(() => {
                    vm.ListErrorLog_AtChargeEnd2.Add(CreateLog(msg, level));

                    if (vm.ListErrorLog_AtChargeEnd2.Count > 50) {
                        vm.ListErrorLog_AtChargeEnd2.RemoveAt(0);
                    }
                    list_error_at_endcharge2.ScrollToBottom();
                });

                try {
                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                    DirectoryHelper.CheckDirectory(dir);
                    string saveFileName = Path.Combine(dir, $"_error_endcharge2.csv");
                    ExcelHelper.AppendToTXTByCache(saveFileName, msg);
                }
                catch (Exception ex) {
                    Log.E("save _error_robot file error", ex);
                }
            }
        }



        private void AddLog2PreCharge2(string msg, EnumLogLevel level = EnumLogLevel.Message) {
            Log.I($"precharge2:{msg}");
            if (level != EnumLogLevel.Error) {
                UIInvoke(() => {
                    vm.ListLog_AtPreCharge2.Add(CreateLog(msg, level));

                    if (vm.ListLog_AtPreCharge2.Count > 50) {
                        vm.ListLog_AtPreCharge2.RemoveAt(0);
                    }
                    list_log_at_precharge2.ScrollToBottom();
                });
            }
            else {
                UIInvoke(() => {
                    vm.ListErrorLog_AtPreCharge2.Add(CreateLog(msg, level));
                    if (vm.ListErrorLog_AtPreCharge2.Count > 50) {
                        vm.ListErrorLog_AtPreCharge2.RemoveAt(0);
                    }
                    list_error_at_precharge2.ScrollToBottom();
                });

                try {
                    string dir = Path.Combine(AppIniData.SaveDir, $"{AppIniData.LotId}");
                    DirectoryHelper.CheckDirectory(dir);
                    string saveFileName = Path.Combine(dir, $"_error_precharge2.csv");
                    ExcelHelper.AppendToTXTByCache(saveFileName, msg);
                }
                catch (Exception ex) {
                    Log.E("save _error_robot file error", ex);
                }
            }
        }

        private bool isClearing = false;
        private void ClearAll() {
            isClearing = true;
            Log.I("clear 1");
            lock (lock_scan) {
                while (IsRunning) {
                    if (!BatteryDataManager.queue_data_at_scan.TryDequeue(out var data)) {
                        Log.I($"clear 1 -1 :{BatteryDataManager.queue_data_at_scan.Count}");
                        break;
                    }
                }
            }

            Log.I("clear 1");
            lock (lock_ccd) {
                while (IsRunning) {
                    if (!BatteryDataManager.queue_fromPlc_ccd.TryDequeue(out var data)) {
                        Log.I($"clear 1 -1 :{BatteryDataManager.queue_fromPlc_ccd.Count}");
                        break;
                    }
                }
            }
            Log.I("clear 3");
            lock (lock_robot) {
                while (IsRunning) {
                    if (!BatteryDataManager.queue_fromPlc_robot.TryDequeue(out var data)) {
                        Log.I($"clear 3 -1 :{BatteryDataManager.queue_fromPlc_robot.Count}");
                        break;
                    }
                }
            }
            Log.I("clear 4");

            lock (lock_ccd_plc) {
                isMatchAtCCD = false;
                isMatchAtCCDTime = 0;
                preBatteryAtScan = null;

                //清空 ccd 的plc 流程

                while (IsRunning) {
                    if (!BatteryDataManager.queue_fromPlc_ccd.TryDequeue(out var data)) {
                        Log.I($"clear 4 -1 :{BatteryDataManager.queue_fromPlc_robot.Count}");
                        break;
                    }
                }

                //清空 ccd 的结果数据
                while (IsRunning) {
                    if (!BatteryDataManager.queue_data_at_scan_ccd.TryDequeue(out var data)) {
                        Log.I($"clear 4 -2 :{BatteryDataManager.queue_data_at_scan_ccd.Count}");
                        break;
                    }
                }

                while (IsRunning) {
                    if (!BatteryDataManager.queue_data_at_scan.TryDequeue(out var data)) {
                        Log.I($"clear 4 -3:{BatteryDataManager.queue_data_at_scan.Count}");
                        break;
                    }
                }
            }
            Log.I("clear 5");
            lock (lock_robot_plc) {
                isMatchAtRobot = false;
                isMatchAtRobotTime = 0;
                preBatteryAtCCD = null;
                //先清空信号 在清空数据
                while (IsRunning) {
                    if (!BatteryDataManager.queue_fromPlc_robot.TryDequeue(out var data)) {
                        Log.I($"clear 5 -1:{BatteryDataManager.queue_fromPlc_robot.Count}");
                        break;
                    }
                }

                while (IsRunning) {
                    if (!BatteryDataManager.queue_data_at_scan_ccd.TryDequeue(out var data)) {
                        Log.I($"clear 5 -2:{BatteryDataManager.queue_data_at_scan_ccd.Count}");
                        break;
                    }
                }
            }
            Log.I("clear 6");
            seq_jump.ClearSeq();
            seq_same.ClearSeq();
            UIInvoke(() => {
                vm.ListLog_AtScan.Clear();
                vm.ListErrorLog_AtScan.Clear();
                vm.ListLog_AtCCD.Clear();
                vm.ListErrorLog_AtCCD.Clear();
                vm.ListLog_AtRobot.Clear();
                vm.ListErrorLog_AtRobot.Clear();
                vm.ListLog_AtPreCharge.Clear();
                vm.ListErrorLog_AtPreCharge.Clear();
                vm.ListLog_AtPreCharge2.Clear();
                vm.ListErrorLog_AtPreCharge2.Clear();

                vm.ListLog_AtChargeEnd1.Clear();
                vm.ListErrorLog_AtChargeEnd1.Clear();
                vm.ListLog_AtChargeEnd2.Clear();
                vm.ListErrorLog_AtChargeEnd2.Clear();
            });
            checkJumpSeq = 0;
            isCheckJump = false;
            //清空跳码
            isClearing = false;
            Log.I($"clear done");
        }
        private void DoPLC_ReadLotData(object value) {

            //喷码机PLC将批次数据写入82000~D82019 （ASCII）
            //并将D82990至1，待接受PC端交互成功信号D90020.0后D82990至0。
            //D90028.0为报警信号，D90029.0为警告信号

            if (!ReadPLC1(EnumPLCRead.R_6_批次数据, out var obj)) {
                return;
            }
            if (obj is short[] listSeq) {
                var lotData = ArrayToString(listSeq);
                AddUILog($"新批次 :{lotData}!");
                //创建表
                if (AppIniData.LotId != AppIniData.PreLotId) {
                    AppIniData.PreLotId = AppIniData.LotId;
                    AppData.ini.WriteString(nameof(AppIniData.PreLotId), AppIniData.PreLotId);
                    AddUILog($"上个批次 :{AppIniData.PreLotId}!");
                }
                //防止批次乱存
                string lotIdEx = $"{lotData}_{DateTime.Now.AsString("yyyyMMddHHmm")}";
                AppIniData.LotId = lotIdEx;

                UIInvoke(() => {
                    vm.LotId = AppIniData.LotId;
                });

                AppData.ini.WriteString(nameof(AppIniData.LotId), AppIniData.LotId);
                OrderData orderData = new OrderData() {
                    create_time = DateTime.Now,
                    order_no = lotData,
                    order_no_ex = lotIdEx
                };
                localDBHelper.SaveOrderData(orderData);
            }
            AddUILog("新批次,清空历史数据!");
            ClearAll();
            //这里开始读取数据
            SendPLC1(EnumPLCWrite.S_6_读取批次完成, 1);
        }


        private void DoPLC_BeforePreCharge_At1(object value) {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2PreCharge(msg, level); });
            bool hasError = false;
            if (!ReadPLC2(EnumPLCRead.R_4_1_预充电电池序列号_1, out var obj)) {
                LogAction(log, $"1工位读取电池序列号失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (!ReadPLC2(EnumPLCRead.R_4_2_预充电前托盘条码_1, out var obj2)) {
                LogAction(log, $"1工位读取盘条码失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (hasError == false) {
                if (obj is short[] listSeq && obj2 is short[] trayCode) {
                    //先扫码
                    var tray_code = ArrayToString(trayCode);
                    var seqs = ArrayToSeqs(listSeq);
                    LogAction(log, $"开始扫码");
                    if (!AppDevice.com_scaner1.ReadData(out var trya_code_from_device)) {
                        hasError = true;
                        LogAction(log, $"1工位 扫码异常!", EnumLogLevel.Error);
                    }
                    else {
                        LogAction(log, $"1工位 {trya_code_from_device}");
                        if (tray_code != trya_code_from_device) {
                            hasError = true;
                            LogAction(log, $"1工位 条码不匹配! 扫码枪:{trya_code_from_device},PLC:{tray_code}", EnumLogLevel.Error);
                        }
                        else {
                            if (!localDBHelper.GetTrayData(AppIniData.LotId, tray_code, out var trayData)) {
                                LogAction(log, $"1工位 未找到前置批次{AppIniData.LotId}托盘数据 :{tray_code}", EnumLogLevel.Warning);
                                if (!localDBHelper.GetTrayData(AppIniData.PreLotId, tray_code, out trayData)) {
                                    hasError = true;
                                    LogAction(log, $"1工位 {tray_code}未找到前置托盘数据 :{tray_code}", EnumLogLevel.Error);
                                }
                            }
                            if (hasError == false) {
                                if (seqs.Length != trayData.cells.Count) {
                                    hasError = true;
                                    LogAction(log, $" 1工位 {tray_code}序列号数量不匹配! 前置:{trayData.cells.Count},PLC:{seqs.Length}", EnumLogLevel.Error);
                                }
                                else {
                                    for (int i = 0; i < seqs.Length; i++) {
                                        if (seqs[i] != trayData.cells[i]) {
                                            hasError = true;
                                            LogAction(log, $"1工位 {tray_code}序列号不匹配! 索引:{i} 前置:{trayData.cells[i]},PLC:{seqs[i]}", EnumLogLevel.Error);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (hasError) {
                if (AppIniData.IsErrorOnPreCharge) {
                    LogAction(log, $"1工位 发送报警", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_4_预充电前报警_1, 1);
                }
                else if (AppIniData.IsWarningOnPreCharge) {
                    LogAction(log, $"1工位 发送警告", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_4_预充电前警告_1, 1);
                }
            }
            else {
                LogAction(log, $"1工位托盘 校验通过", EnumLogLevel.Scuess);
            }
            //这里开始读取数据
            SendPLC2(EnumPLCWrite.S_4_预充电前完成_1, 1);
        }
        private void DoPLC_BeforePreCharge_At2(object value) {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2PreCharge2(msg, level); });
            bool hasError = false;
            if (!ReadPLC2(EnumPLCRead.R_5_1_预充电电池序列号_2, out var obj)) {
                LogAction(log, $"2工位读取电池序列号失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (!ReadPLC2(EnumPLCRead.R_5_2_预充电前托盘条码_2, out var obj2)) {
                LogAction(log, $"2工位读取盘条码失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (hasError == false) {
                if (obj is short[] listSeq && obj2 is short[] trayCode) {
                    //先扫码
                    var tray_code = ArrayToString(trayCode);
                    var seqs = ArrayToSeqs(listSeq);
                    LogAction(log, $"开始扫码");
                    if (!AppDevice.com_scaner2.ReadData(out var trya_code_from_device)) {
                        hasError = true;
                        LogAction(log, $"2工位 扫码异常!", EnumLogLevel.Error);
                    }
                    else {
                        LogAction(log, $"2工位 扫码 :{trya_code_from_device} plc :{tray_code}");
                        if (tray_code != trya_code_from_device) {
                            hasError = true;
                            LogAction(log, $"2工位 条码不匹配! 扫码枪:{trya_code_from_device},PLC:{tray_code}", EnumLogLevel.Error);
                        }
                        else {
                            if (!localDBHelper.GetTrayData(AppIniData.LotId, tray_code, out var trayData)) {
                                LogAction(log, $"2工位 未找到前置批次{AppIniData.LotId}托盘数据 :{tray_code}", EnumLogLevel.Warning);
                                if (!localDBHelper.GetTrayData(AppIniData.PreLotId, tray_code, out trayData)) {
                                    hasError = true;
                                    LogAction(log, $"2工位 未找到前置 {AppIniData.LotId}/{AppIniData.PreLotId} 托盘数据 :{tray_code}", EnumLogLevel.Error);
                                }
                            }
                            if (hasError == false) {
                                if (seqs.Length != trayData.cells.Count) {
                                    hasError = true;
                                    LogAction(log, $"2工位 序列号数量不匹配! 前置:{trayData.cells.Count},PLC:{seqs.Length}", EnumLogLevel.Error);
                                }
                                else {
                                    for (int i = 0; i < seqs.Length; i++) {
                                        if (seqs[i] != trayData.cells[i]) {
                                            hasError = true;
                                            LogAction(log, $"2工位 序列号不匹配! 索引:{i} 前置:{trayData.cells[i]},PLC:{seqs[i]}", EnumLogLevel.Error);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (hasError) {
                if (AppIniData.IsErrorOnPreCharge2) {
                    LogAction(log, $"2工位 发送报警", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_5_预充电前报警_2, 1);
                }
                else if (AppIniData.IsWarningOnPreCharge2) {
                    LogAction(log, $"2工位 发送警告", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_5_预充电前警告_2, 1);
                }
            }
            else {
                LogAction(log, $"2工位托盘 校验通过", EnumLogLevel.Scuess);
            }
            //这里开始读取数据
            SendPLC2(EnumPLCWrite.S_5_预充电前完成_2, 1);
        }
        private void DoPLC_EndCharge_At1(object value) {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2EndCharge(msg, level); });
            bool hasError = false;
            if (!ReadPLC2(EnumPLCRead.R_7_1_预充电后电池序列号_1, out var obj)) {
                LogAction(log, $"1工位读取电池序列号失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (!ReadPLC2(EnumPLCRead.R_7_2_预充电后托盘条码_1, out var obj2)) {
                LogAction(log, $"1工位读取盘条码失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (hasError == false) {
                if (obj is short[] listSeq && obj2 is short[] trayCode) {
                    //先扫码
                    var tray_code = ArrayToString(trayCode);
                    var seqs = ArrayToSeqs(listSeq);
                    LogAction(log, $"1工位 条码 plc :{tray_code}");

                    if (!localDBHelper.GetTrayData(AppIniData.LotId, tray_code, out var trayData)) {
                        LogAction(log, $"1工位 无批次{AppIniData.LotId}托盘数据 :{tray_code}", EnumLogLevel.Warning);
                        if (!localDBHelper.GetTrayData(AppIniData.PreLotId, tray_code, out trayData)) {
                            hasError = true;
                            LogAction(log, $"1工位 无前批次{AppIniData.LotId}/{AppIniData.PreLotId} 数据 :{tray_code}", EnumLogLevel.Error);
                        }
                    }
                    if (hasError == false) {
                        if (trayData.is_same) {
                            hasError = true;
                            LogAction(log, $"1工位 {trayData.tray_code} 重码排出", EnumLogLevel.Error);
                        }
                        else if (trayData.is_jump) {
                            hasError = true;
                            LogAction(log, $"1工位 {trayData.tray_code} 跳码排出", EnumLogLevel.Error);
                        }
                        else {
                            if (seqs.Length != trayData.cells.Count) {
                                hasError = true;
                                LogAction(log, $"1工位 序列号数量不匹配! 前置:{trayData.cells.Count},PLC:{seqs.Length}", EnumLogLevel.Error);
                            }
                            else {
                                for (int i = 0; i < seqs.Length; i++) {
                                    if (seqs[i] != trayData.cells[i]) {
                                        hasError = true;
                                        LogAction(log, $"1工位 序列号不匹配! 索引:{i} 前置:{trayData.cells[i]},PLC:{seqs[i]}", EnumLogLevel.Error);
                                    }
                                }
                                LogAction(log, $"1工位 开始抓预充电软件{tray_code}");
                                if (!GetTrayDataFromDir(tray_code, out var trayFromPre)) {
                                    LogAction(log, $"1工位 读取预充电软件{tray_code}数据异常!", EnumLogLevel.Error);
                                    hasError = true;
                                }
                                else {
                                    LogAction(log, $"1工位 开始匹配预充电软件{tray_code}");
                                    if (trayFromPre.TrayID != tray_code) {
                                        LogAction(log, $"1工位 {tray_code}与预充电软件{trayFromPre.TrayID}不匹配!", EnumLogLevel.Error);
                                        hasError = true;
                                    }
                                    else {
                                        if (seqs.Length != trayFromPre.Cells.Count) {
                                            LogAction(log, $"1工位 {tray_code}与软件{trayFromPre.TrayID}序列号数不匹配!", EnumLogLevel.Error);
                                            hasError = true;
                                        }
                                        for (int i = 0; i < seqs.Length; i++) {
                                            if (seqs[i] != trayFromPre.Cells[i].Serial) {
                                                hasError = true;
                                                LogAction(log, $"1工位 {tray_code}与软件序列号不匹配! 索引:{i},{seqs[i]}/{trayFromPre.Cells[i].Serial}", EnumLogLevel.Error);
                                            }
                                        }
                                    }
                                }

                            }
                        }
                    }
                }
            }

            if (hasError) {
                if (AppIniData.IsErrorOnEndCharge) {
                    LogAction(log, $"1工位 有异常 排出", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_7_预充电后报警_1, 1);
                }
            }
            else {
                LogAction(log, $"1工位托盘 校验通过", EnumLogLevel.Scuess);
                if (ngOut) {
                    LogAction(log, $"1工位 强制 排出", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_7_预充电后报警_1, 1);
                }
            }
            //这里开始读取数据
            SendPLC2(EnumPLCWrite.S_7_预充电后完成_1, 1);
        }

        private bool GetTrayDataFromDir(string tray_code, out TrayFromPreCharge trayData) {
            trayData = null;
            try {
                Log.I($"开始找预充电软件托盘 :{tray_code}");

                if (AppIniData.Dir_PreCharge.Null()) {
                    Log.I($"目录为空 :{AppIniData.Dir_PreCharge}");
                    return false;
                }
                Log.I($"目录 :{AppIniData.Dir_PreCharge}");

                FileInfo latestFile = null;
                var dt = DateTime.Now;
                string dir1 = Path.Combine(AppIniData.Dir_PreCharge, $"{dt.Year.ToString().Right(2)}{dt.ToString("MMdd")}");
                string dir2 = Path.Combine(AppIniData.Dir_PreCharge, $"{dt.Year.ToString().Right(2)}{dt.AddDays(-1).ToString("MMdd")}");

                while (IsRunning) {
                    if ((DateTime.Now - dt).TotalSeconds >= AppIniData.EndChargeWaitOuttime) {
                        Log.I($"读取预充电软件文件超时");
                        return false;
                    }
                    try {
                        latestFile = Directory.EnumerateFiles(dir1, "*.csv", SearchOption.AllDirectories)
                                              .Where(file => System.IO.Path.GetFileName(file).StartsWith(tray_code))
                                              .Select(file => new FileInfo(file))
                                              .OrderByDescending(fileInfo => fileInfo.LastWriteTime)
                                              .FirstOrDefault();
                    }
                    catch (Exception ex) {
                        Log.I($"读取预充电软件文件异常1 {dir1}");
                    }
                    if (latestFile == null) {
                        Log.I($"找不到预充电软件托盘 :{dir1}");
                        try {
                            latestFile = Directory.EnumerateFiles(dir2, "*.csv", SearchOption.AllDirectories)
                                              .Where(file => System.IO.Path.GetFileName(file).StartsWith(tray_code))
                                              .Select(file => new FileInfo(file))
                                              .OrderByDescending(fileInfo => fileInfo.LastWriteTime)
                                              .FirstOrDefault();
                        }
                        catch (Exception ex) {
                            Log.I($"读取预充电软件文件异常2 {dir1}");
                        }
                    }

                    if (latestFile == null) {
                        Log.I($"读取预充电软件托盘失败 :{dir2}");
                        Thread.Sleep(10);
                        continue;
                    }
                    else {
                        Log.I($"读取预充电软件文件OK,开始解析 {latestFile.FullName}");
                        return TrayDataHelper.ParseBatteryData(latestFile.FullName, out trayData);
                    }
                }
                //开始解析
                return false;
            }
            catch (Exception ex) {
                Log.E("读取预充电软件托盘异常", ex);
                return false;
            }

        }

        private void DoPLC_EndCharge_At2(object value) {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2EndCharge2(msg, level); });
            bool hasError = false;
            if (!ReadPLC2(EnumPLCRead.R_8_1_预充电后电池序列号_2, out var obj)) {
                LogAction(log, $"2工位读取电池序列号失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (!ReadPLC2(EnumPLCRead.R_8_2_预充电后托盘条码_2, out var obj2)) {
                LogAction(log, $"2工位读取盘条码失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (hasError == false) {
                if (obj is short[] listSeq && obj2 is short[] trayCode) {
                    //先扫码
                    var tray_code = ArrayToString(trayCode);
                    var seqs = ArrayToSeqs(listSeq);
                    LogAction(log, $"2工位 条码 plc :{tray_code}");

                    if (!localDBHelper.GetTrayData(AppIniData.LotId, tray_code, out var trayData)) {
                        LogAction(log, $"2工位 无批次{AppIniData.LotId}托盘数据 :{tray_code}", EnumLogLevel.Warning);
                        if (!localDBHelper.GetTrayData(AppIniData.PreLotId, tray_code, out trayData)) {
                            hasError = true;
                            LogAction(log, $"2工位 无前批次{AppIniData.LotId}/{AppIniData.PreLotId} 托盘数据 :{tray_code}", EnumLogLevel.Error);
                        }
                    }
                    if (hasError == false) {
                        if (trayData.is_same) {
                            hasError = true;
                            LogAction(log, $"2工位 {trayData.tray_code} 重码排出", EnumLogLevel.Error);
                        }
                        else if (trayData.is_jump) {
                            hasError = true;
                            LogAction(log, $"2工位 {trayData.tray_code} 跳码排出", EnumLogLevel.Error);
                        }
                        else if (trayData.iserror_at_robot) {
                            hasError = true;
                            LogAction(log, $"2工位 {trayData.tray_code} 机械手异常排出", EnumLogLevel.Error);
                        }
                        else {
                            if (seqs.Length != trayData.cells.Count) {
                                hasError = true;
                                LogAction(log, $"2工位 序列号数量不匹配! 前置:{trayData.cells.Count},PLC:{seqs.Length}", EnumLogLevel.Error);
                            }
                            else {
                                for (int i = 0; i < seqs.Length; i++) {
                                    if (seqs[i] != trayData.cells[i]) {
                                        hasError = true;
                                        LogAction(log, $"2工位 序列号不匹配! 索引:{i} 前置:{trayData.cells[i]},PLC:{seqs[i]}", EnumLogLevel.Error);
                                    }
                                }
                                LogAction(log, $"2工位 开始抓预充电软件{tray_code}");
                                if (!GetTrayDataFromDir(tray_code, out var trayFromPre)) {
                                    LogAction(log, $"2工位 读取预充电软件{tray_code}数据异常!", EnumLogLevel.Error);
                                    hasError = true;
                                }
                                else {
                                    LogAction(log, $"2工位 开始匹配预充电软件{tray_code}");
                                    if (trayFromPre.TrayID != tray_code) {
                                        LogAction(log, $"2工位 {tray_code}与预充电软件{trayFromPre.TrayID}不匹配!", EnumLogLevel.Error);
                                        hasError = true;
                                    }
                                    else {
                                        if (seqs.Length != trayFromPre.Cells.Count) {
                                            LogAction(log, $"2工位 {tray_code}与软件{trayFromPre.TrayID}序列号数不匹配!", EnumLogLevel.Error);
                                            hasError = true;
                                        }
                                        for (int i = 0; i < seqs.Length; i++) {
                                            if (seqs[i] != trayFromPre.Cells[i].Serial) {
                                                hasError = true;
                                                LogAction(log, $"2工位 {tray_code}与软件序列号不匹配! 索引:{i},{seqs[i]}/{trayFromPre.Cells[i].Serial}", EnumLogLevel.Error);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (hasError) {
                if (AppIniData.IsErrorOnEndCharge2) {
                    LogAction(log, $"2工位 有异常,排出", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_8_预充电后报警_2, 1);
                }
            }
            else {
                LogAction(log, $"2工位托盘 校验通过", EnumLogLevel.Scuess);
                if (ngOut2) {
                    LogAction(log, $"2工位 强制排出", EnumLogLevel.Warning);
                    SendPLC2(EnumPLCWrite.S_8_预充电后报警_2, 1);
                }
            }
            //这里开始读取数据
            SendPLC2(EnumPLCWrite.S_8_预充电后完成_2, 1);
        }


        private void DoPLC_AtRobot(object value) {
            Action<string, EnumLogLevel> log = ((msg, level) => { AddLog2Robot(msg, level); });
            bool hasError = false;
            if (!ReadPLC2(EnumPLCRead.R_3_1_机械手电池序列号数据, out var obj)) {
                LogAction(log, $"读取 R_3_1_机械手电池序列号数据 失败!", EnumLogLevel.Error);
                hasError = true;
            }

            if (!ReadPLC2(EnumPLCRead.R_3_2_机械手托盘条码, out var obj3)) {
                LogAction(log, $"读取 R_3_2_机械手托盘条码 失败!", EnumLogLevel.Error);
                hasError = true;
            }
            if (hasError == false) {
                if (obj is short[] listSeq && obj3 is short[] robot_traycode) {

                    var tray_code = ArrayToString(robot_traycode);
                    var seqNos = new List<int>();
                    for (int i = 0; i < listSeq.Length; i += 3) {
                        var seqNo = ArrayToSeq(listSeq[i], listSeq[i + 1]);
                        seqNos.Add(seqNo);
                    }
                    BatteryDataManager.queue_fromPlc_robot.Enqueue(new PLCDataAtRobot() {
                        Time = DateTime.Now,
                        tray_code = tray_code,
                        seqNos = seqNos
                    });
                }
                //这里开始读取数据
            }
            if (hasError) {
                if (AppIniData.IsErrorOnRobot) {
                    LogAction(log, $"发送机械手报警", EnumLogLevel.Warning);
                    SendPLC1(EnumPLCWrite.S_1_2_扫码枪后报警, 1);
                }
                else if (AppIniData.IsWarningOnRobot) {
                    //LogAction(log,$"发送机械手警告", EnumLogLevel.Warning);
                    //SendPLC1(EnumPLCWrite.S_3_1_机械手电池序列号警告, 1);
                }
            }
            SendPLC2(EnumPLCWrite.S_3_机械手电池序列号完成, 1);
        }

        private void BtnClick_CfgErrorWarn(object sender, RoutedEventArgs e) {
            SaveCfgErrorWarn();

        }

        void SaveCfgErrorWarn() {

            AppIniData.IsErrorOnScan = vm.IsErrorOnScan;
            AppIniData.IsWarningOnScan = vm.IsWarningOnScan;
            AppIniData.IsErrorOnCCD = vm.IsErrorOnCCD;
            AppIniData.IsWarningOnCCD = vm.IsWarningOnCCD;
            AppIniData.IsErrorOnRobot = vm.IsErrorOnRobot;
            AppIniData.IsWarningOnRobot = vm.IsWarningOnRobot;

            AppIniData.IsWarningOnPreCharge = vm.IsWarningOnPreCharge;
            AppIniData.IsErrorOnPreCharge = vm.IsErrorOnPreCharge;
            AppIniData.IsWarningOnPreCharge2 = vm.IsWarningOnPreCharge2;
            AppIniData.IsErrorOnPreCharge2 = vm.IsErrorOnPreCharge2;

            AppIniData.IsErrorOnEndCharge = vm.IsErrorOnEndCharge;


            AppIniData.IsErrorOnEndCharge2 = vm.IsErrorOnEndCharge2;

            AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnScan), AppIniData.IsErrorOnScan);
            AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnScan), AppIniData.IsWarningOnScan);

            AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnCCD), AppIniData.IsErrorOnCCD);
            AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnCCD), AppIniData.IsWarningOnCCD);

            AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnRobot), AppIniData.IsErrorOnRobot);
            AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnRobot), AppIniData.IsWarningOnRobot);

            AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnPreCharge), AppIniData.IsWarningOnPreCharge);
            AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnPreCharge), AppIniData.IsErrorOnPreCharge);

            AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnPreCharge2), AppIniData.IsWarningOnPreCharge2);
            AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnPreCharge2), AppIniData.IsErrorOnPreCharge2);

            AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnEndCharge), AppIniData.IsErrorOnEndCharge);
            AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnEndCharge2), AppIniData.IsErrorOnEndCharge2);
        }

        void InitCfgErrorWarn() {
            vm.IsErrorOnScan = AppIniData.IsErrorOnScan;
            vm.IsWarningOnScan = AppIniData.IsWarningOnScan;

            vm.IsErrorOnCCD = AppIniData.IsErrorOnCCD;
            vm.IsWarningOnCCD = AppIniData.IsWarningOnCCD;
            vm.IsErrorOnRobot = AppIniData.IsErrorOnRobot;
            vm.IsWarningOnRobot = AppIniData.IsWarningOnRobot;
            vm.IsWarningOnPreCharge = AppIniData.IsWarningOnPreCharge;
            vm.IsErrorOnPreCharge = AppIniData.IsErrorOnPreCharge;
            vm.IsWarningOnPreCharge2 = AppIniData.IsWarningOnPreCharge2;
            vm.IsErrorOnPreCharge2 = AppIniData.IsErrorOnPreCharge2;
            vm.IsErrorOnEndCharge = AppIniData.IsErrorOnEndCharge;
            vm.IsErrorOnEndCharge2 = AppIniData.IsErrorOnEndCharge2;
            vm.LotId = AppIniData.LotId;

            //AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnCCD), AppIniData.IsErrorOnCCD);
            //AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnCCD), AppIniData.IsWarningOnCCD);
            //AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnRobot), AppIniData.IsErrorOnRobot);
            //AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnRobot), AppIniData.IsWarningOnRobot);
            //AppData.ini.WriteBool(nameof(AppIniData.IsWarningOnPreCharge), AppIniData.IsWarningOnPreCharge);
            //AppData.ini.WriteBool(nameof(AppIniData.IsErrorOnPreCharge), AppIniData.IsErrorOnPreCharge);
        }
        private void BtnClick_Cfg_Device(object sender, RoutedEventArgs e) {
            new View_Config_Device(new List<DeviceBase> {
                AppDevice.com_scaner1,
                AppDevice.com_scaner2,
            }, m_DeviceHelper.ini).ShowDialog();
            MessageBox.Show("配置后需要重新启动");
        }
        private void BtnClick_PLCMonitor(object sender, RoutedEventArgs e) {
            new ViewPLCControl(AppDevice.plc1, AppCreator.m_PLCCreator1.GroupParas()).Show();
        }

        private void BtnClick_ClearErrorOnScan(object sender, RoutedEventArgs e) {
            vm.ListErrorLog_AtScan.Clear();
        }

        private void BtnClick_ClearErrorOnCCD(object sender, RoutedEventArgs e) {
            vm.ListErrorLog_AtCCD.Clear();
        }

        private void BtnClick_ClearErrorOnPreCharge2(object sender, RoutedEventArgs e) {
            vm.ListErrorLog_AtPreCharge2.Clear();
        }

        private void BtnClick_ClearErrorOnPreCharge(object sender, RoutedEventArgs e) {
            vm.ListErrorLog_AtPreCharge.Clear();
        }

        private void BtnClick_ClearErrorOnRobot(object sender, RoutedEventArgs e) {
            vm.ListErrorLog_AtRobot.Clear();
        }



        private async void BtnClick_Init(object sender, RoutedEventArgs e) {
            if (MessageBox.Show("", "是否初始化数据?", MessageBoxButton.YesNo) != MessageBoxResult.Yes) {
                return;
            }
            await Run(() => ClearAll());
            MessageBox.Show("已清理!");
        }

        private void BtnClick_CfgSystem(object sender, RoutedEventArgs e) {
            new ViewCfgSystem().ShowDialog();
        }

        private void BtnClick_ClearErrorOnEndCharge(object sender, RoutedEventArgs e) {
            vm.ListErrorLog_AtChargeEnd1.Clear();
        }

        private void BtnClick_ClearErrorOnEndCharge2(object sender, RoutedEventArgs e) {
            vm.ListErrorLog_AtChargeEnd2.Clear();
        }

        private void BtnClick_ReMatch(object sender, RoutedEventArgs e) {
            isMatchAtCCDTime = 0;
            isMatchAtCCD = false;
            MessageBox.Show("已重新匹配");
        }
        private void BtnClick_ReMatch_AtRobot(object sender, RoutedEventArgs e) {
            isMatchAtRobotTime = 0;
            isMatchAtRobot = false;
            MessageBox.Show("已重新匹配");
        }
        private bool ngOut = false;
        private bool ngOut2 = false;
        private void BtnClick_NGOut(object sender, RoutedEventArgs e) {
            ngOut = vm.NGOut;
        }
        private async void BtnClick_Scaner1_Test(object sender, RoutedEventArgs e) {
            var data = "";
            var ret = await Run(() => {
                return AppDevice.com_scaner1.ReadData(out data);

            });
            if (ret == false) {
                MessageBox.Show("1 读取失败!");
            }
            else {
                MessageBox.Show($"1 读取成功 : {data}");
            }
        }
        private async void BtnClick_Scaner2_Test(object sender, RoutedEventArgs e) {
            var data = "";
            var ret = await Run(() => {
                return AppDevice.com_scaner2.ReadData(out data);

            });
            if (ret == false) {
                MessageBox.Show("2 读取失败!");
            }
            else {
                MessageBox.Show($"2 读取成功 : {data}");
            }
        }
        private System.Threading.Timer timerClose;
        private int tick = 0;
        private void initTimer() {
            timerClose = new System.Threading.Timer(timerCall, null, 1000, 1000);
        }

        private EnumClearFlag clearFlag = EnumClearFlag.正常;

        private object lockTimer = new object();
        DateTime dt = DateTime.Now;
        private void timerCall(object obj) {
            if (Monitor.TryEnter(lockTimer)) {
                try {
                    if ((DateTime.Now - dt).TotalSeconds >= 3) {
                        SendPLC1(EnumPLCWrite.S_0_心跳_1, 1, false);
                        SendPLC2(EnumPLCWrite.S_0_心跳_2, 1, false);
                        tick++;
                        dt = DateTime.Now;
                    }
                }
                finally {
                    Monitor.Exit(lockTimer);
                }
            }
        }
        private void BtnClick_ClearLine(object sender, RoutedEventArgs e) {
            if (MessageBox.Show("是否确定清线?", "提示!", MessageBoxButton.YesNo) != MessageBoxResult.Yes) {
                return;
            }
            ViewEditPassword viewEditPassword = new ViewEditPassword("EVE123");
            if (viewEditPassword.ShowDialog() != true) {
                return;
            }

            SendPLC1(EnumPLCWrite.S_9_清线报警_1, 1);
            SendPLC2(EnumPLCWrite.S_9_清线报警_2, 1);

            MessageBox.Show("已发送清线!");
        }

        private void BtnClick_NGOut2(object sender, RoutedEventArgs e) {
            ngOut2 = vm.NGOut2;
        }
        //private void DoPLC_AtToPreCharge(object value)
        //{
        //    if (value is bool isSignal && isSignal == true)
        //    {

        //    }
        //    else
        //    {
        //        return;
        //    }

        //    if (!ReadPLC(EnumPLCRead.R_7_转预充电机械手位置, out var obj))
        //    {
        //        return;
        //    }
        //    if (!ReadPLC(EnumPLCRead.R_8_转预充电机械手托盘号, out var obj2))
        //    {
        //        return;
        //    }
        //    if (!ReadPLC(EnumPLCRead.R_6_转预充电机械手电池序列号数据, out var obj3))
        //    {
        //        return;
        //    }

        //    if (!ReadPLC(EnumPLCRead.R_10_转预充电预充电位置, out var obj_1))
        //    {
        //        return;
        //    }
        //    if (!ReadPLC(EnumPLCRead.R_11_转预充电预充电托盘号, out var obj2_1))
        //    {
        //        return;
        //    }
        //    if (!ReadPLC(EnumPLCRead.R_9_转预充电预充电电池序列号数据, out var obj3_1))
        //    {
        //        return;
        //    }

        //    if (obj is int[] listSeq
        //        && obj2 is int robot_pos
        //        && obj3 is int robot_traycode

        //        && obj_1 is int[] listSeq_1
        //        && obj2_1 is int robot_pos_1
        //        && obj3_1 is int robot_traycode_1
        //        )
        //    {
        //        List<int> list = new List<int>();
        //        for (int i = 0; i < listSeq.Length; i++)
        //        {
        //            var battery_seqno = listSeq[i];
        //            list.Add(battery_seqno);
        //        }

        //        List<int> list2 = new List<int>();
        //        for (int i = 0; i < listSeq_1.Length; i++)
        //        {
        //            var battery_seqno = listSeq_1[i];
        //            list2.Add(battery_seqno);
        //        }
        //        BatteryDataManager.AddToPre(new RobotBattery_ToPre
        //        {
        //            ListBatteryId = list,
        //            RobotPos = robot_pos,
        //            RobotTraycode = robot_traycode,

        //            ListBatteryId_ToPre = list2,
        //            RobotPos_ToPre = robot_pos_1,
        //            RobotTraycode_ToPre = robot_traycode_1,
        //        });
        //    }
        //    //这里开始读取数据
        //    SendPLC(EnumPLCWrite.S_4_预充电前完成_1, 1);
        //}

    }
}
